package com.member.mp.aop;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.member.common.exception.ServiceException;
import com.member.mp.web.user.SupperController;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author mjyang
 * @date 2022/4/1 11:01
 */
@Aspect
@Slf4j
@Component
@Order(1)
public class DistributeLockAspect extends SupperController {

    private final RedissonClient redissonClient;

    public DistributeLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Pointcut("@annotation(com.member.mp.aop.DistributeLock)")
    public void pointCut() {}

    @Around("pointCut()")
    public Object myAround(ProceedingJoinPoint point) throws Throwable {
        Class<?> targetClass = point.getTarget().getClass();
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = targetClass.getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        DistributeLock distributeLock = AnnotationUtil.getAnnotation(method, DistributeLock.class);
        if (distributeLock == null) {
            return point.proceed();
        }
        Object[] arguments = point.getArgs();
        if (ArrayUtil.isEmpty(arguments) && StrUtil.isBlank(distributeLock.cacheKey())) {
            throw new ServiceException("该方法至少需要拥有一个参数或请设置 cacheKey 参数");
        }
        String cacheKey = null;
        if (ArrayUtil.isEmpty(arguments)) {
            cacheKey = distributeLock.cacheKey();
        } else {
            if (arguments.length == 1) {
                if (arguments[0].getClass().isAssignableFrom(String.class) || arguments[0].getClass().isPrimitive()) {
                    cacheKey = Convert.toStr(arguments[0]);
                }
            }
            if (StrUtil.isBlank(cacheKey)) {
                cacheKey = Convert.toStr(JSONUtil.toJsonStr(arguments).hashCode());
            }
        }
        String prefix = distributeLock.prefix();
        String userId = StrUtil.EMPTY;
        if (distributeLock.appendUserId()) {
            userId = getUserId().toString();
            userId += StrUtil.COLON;
        }
        log.info("prefix => {} | userId => {} | cacheKey => {}", prefix, userId, cacheKey);
        if (redissonClient == null) {
            log.error("==========服务异常==========");
            throw new ServiceException("服务异常，请稍后再试");
        }
        RLock lock = this.redissonClient.getLock(prefix + userId + cacheKey);
        try {
            if (lock.tryLock(distributeLock.lockTime(), TimeUnit.SECONDS)) {
                return point.proceed();
            }
            throw new ServiceException("操作过于频繁，请稍后再试");
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
